package gu.sql2java.druid;

import java.io.Closeable;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.regex.Pattern;

import javax.sql.DataSource;

import com.google.common.base.Throwables;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

import gu.sql2java.DataSourceConfig;
import gu.sql2java.DataSourceFactory;
import gu.sql2java.utils.CaseSupport;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Strings.nullToEmpty;
import static gu.sql2java.SimpleLog.log;
import static gu.sql2java.DataSourceConfig.logDatabaseProperties;
import static com.alibaba.druid.pool.DruidDataSourceFactory.PROP_URL; 
import static com.alibaba.druid.pool.DruidDataSourceFactory.PROP_DRIVERCLASSNAME; 
import static com.alibaba.druid.pool.DruidDataSourceFactory.PROP_USERNAME; 
import static com.alibaba.druid.pool.DruidDataSourceFactory.PROP_PASSWORD; 

/**
 * 基于druid实现{@link DataSourceFactory}接口
 * @author guyadong
 *
 */
public class DruidDataSourceFactory implements DataSourceFactory{
	private static final String jdbcPrefix = "jdbc.";
	private static final String c3p0Prefix = "c3p0.";
	private static final String springDatasourcePrefix = "spring.datasource.";
	private static final String springDatasourceDruidPrefix = "spring.datasource.druid.";
	private static final String druidPrefix = "druid.";
	private static final String driverSuffix = ".driver";
	/** {@link com.alibaba.druid.pool.DruidDataSourceFactory}所有属性名 */
	private static final Set<String> druidProperyNames = Sets.newHashSet(getDuridPropertyNames().values());
	public DruidDataSourceFactory() {
	}
	
    /** 
     * if not contain sub key without prefix,rename key to sub key without prefix ,and convert to camel-case string
     */
    private static boolean renameKey(Properties props,String key,String prefix){
    	return renameKey(props,key,prefix,null);
    }
    /**
     * if not contain sub key without prefix,rename key to sub key without prefix ,and convert to camel-case string
     * @param props
     * @param key 
     * @param prefix
     * @param newPrefix new prefix if not empty or null
     */
    private static boolean renameKey(Properties props,String key,String prefix,String newPrefix){
    	if(key.startsWith(prefix)){
    		Object value = props.remove(key);
    		String subKey = key.substring(prefix.length());
    		String camelcaseKey = subKey;
    		if(Pattern.compile("[-_]").matcher(subKey).find()){
    			camelcaseKey = CaseSupport.toCamelcase(subKey.replace('-', '_').toLowerCase());    			
    		}
    		/** 统一改为驼峰命名(camel-case) */
    		if(!props.containsKey(camelcaseKey) && !props.containsKey(nullToEmpty(newPrefix) + camelcaseKey)){
    			props.put(nullToEmpty(newPrefix) + camelcaseKey, value);
    		}
    		return true;
    	}
    	return false;
    }
    
    private static void replaceKey(Properties props,String from,String to){
		if(props.containsKey(from)){
			/** 对于命名格式不匹配的参数改名 */
			Object value = props.remove(from);
			props.put(to, value);
		}else if(props.containsKey(druidPrefix + from)){
			/** 对于命名格式不匹配的参数改名 */
			Object value = props.remove(druidPrefix + from);
			props.put(to, value);
		}
	}

	/** 
     * 归一化{@link Properties}配置参数<br>
     * 如果参数名有{@code prefix}前缀,则将参数前缀改为{@link newPrefix},参数名改为驼峰命名,
     * 如果参数名有 c3p0.前缀则将参数删除,
     * 将driver参数改名为driverClassName,
     */
    private static void normalizePropertiesForDurid0(Properties props,String prefix,String newPrefix){
		props.stringPropertyNames().forEach(k->{
			if(k.startsWith(prefix)){
				/** rename key ,remove  jdbc. */
				renameKey(props, k, prefix,newPrefix);
			}else if (k.startsWith(c3p0Prefix)){
    			/** remove key with prefix c3p0. */
    			props.remove(k);
    			return ;
    		}
			if(k.endsWith(driverSuffix)){
				/** rename .driver to .driverClassName */
				Object value = props.remove("driver");
				props.put(PROP_DRIVERCLASSNAME, value);
			}
		});
    }
    /** 
     * 将{@link Properties}配置归一化为 Druid 可识别的配置参数
     * 如果参数名有 jdbc.,spring.datasource.druid.,spring.datasource.,druid.前缀,则将参数改名(camel-case)删除前缀
     * 如果参数名有 c3p0.前缀,或非单级参数(包含.)则将参数删除
     * 将driver参数改名为driverClassName
     * 删除所有非druid参数名
     */
    static void normalizePropertiesForDurid(Properties props){
    	normalizePropertiesForDurid0(props,jdbcPrefix,null);
    	normalizePropertiesForDurid0(props,druidPrefix,druidPrefix);
    	normalizePropertiesForDurid0(props,springDatasourceDruidPrefix,druidPrefix);
    	normalizePropertiesForDurid0(props,springDatasourcePrefix,null);

    	/** 检查props中的参数名 */
    	druidProperyNames.forEach(n->{
    		if(props.containsKey(n)){
    			// DO NOTHING
    		}else if(props.containsKey(druidPrefix + n)){
    			renameKey(props, druidPrefix + n, druidPrefix);
    		}else if(n.startsWith(druidPrefix)){
    			// DO NOTHING
    		}else	if(Pattern.compile("[-_]").matcher(n).find()){
    			/** 转为驼峰命名尝试查找 */
    			String camelcaseKey = CaseSupport.toCamelcase(n.replace('-', '_').toLowerCase());
    			replaceKey(props,camelcaseKey,n);
    		}else{
    			/** 转为鱼骨命名尝试查找 */
    			String spinalcaseKey = CaseSupport.toSnakecase(n).replace('_', '-');
    			replaceKey(props,spinalcaseKey,n);
    		}
    	});
    	props.stringPropertyNames().forEach(k->{
    		if (k.indexOf('.') >= 0 && !k.startsWith(druidPrefix)){
    			/** remove any key with dot */
    			props.remove(k);
    			return ;
    		}
    	});
    }
    /**
     * 从{@link com.alibaba.druid.pool.DruidDataSourceFactory}获取所有PROP_前缀的静态常量值<br>
     * <ul>
     * <li>K --- 常量字段名</li>
     * <li>V --- 常量字段值(DRUID配置属性名)</li>
     * </ul>
     */
    private static Map<String, String> getDuridPropertyNames(){
    	Iterable<Field> constFields = Iterables.filter(Arrays.asList(com.alibaba.druid.pool.DruidDataSourceFactory.class.getFields()), 
				f->f.getName().startsWith("PROP_") && f.getType().equals(String.class) && Modifier.isStatic(f.getModifiers()) && Modifier.isFinal(f.getModifiers()));
		Map<String, String>  duridConstPopertyNames = Maps.newHashMap();
		constFields.forEach(f->{
			try {
				duridConstPopertyNames.put(f.getName(), (String)f.get(null));
			} catch (IllegalArgumentException | IllegalAccessException e) {
				Throwables.throwIfUnchecked(e);
				throw new RuntimeException(e);
			}
		});
		return duridConstPopertyNames;
    }
    /**
	 * 根据{@link Properties}提供的参数创建{@link DataSource}实例
	 * @param input
	 */
	public static DataSource createDataSource(Properties input){
		Properties props = new Properties();
		props.putAll(checkNotNull(input,"props is null"));
		try{
			normalizePropertiesForDurid(props);
			checkArgument(props.containsKey(PROP_URL),"MISSING ESSENTIAL ARGUMENT %s",PROP_URL);
			logDatabaseProperties(props, "druid");
			return com.alibaba.druid.pool.DruidDataSourceFactory.createDataSource(props);
		}catch (Exception e){
			String message = String.format("can't get connection by argument...url/username/password[%s/%s/%s]",
					props.getProperty(PROP_URL),props.getProperty(PROP_USERNAME),props.getProperty(PROP_PASSWORD));
			throw new IllegalArgumentException(message,e);
		}
	}

	@Override
	public DataSource createDataSource(DataSourceConfig config){
		Properties props = new Properties();
		props.putAll(checkNotNull(config,"config is null").getInitProperties());
		return createDataSource(props);

	}
	@Override
	public void destroy(DataSource dataSource){
        try{
        	if(dataSource instanceof Closeable){
        		((Closeable)dataSource).close();
        	}
        }catch (Exception e) {
            log("dispose DruidDataSource wrong ..." + e);
        }
	}
}
